<?php
// 应用公共文件

error_reporting(E_ERROR | E_WARNING | E_PARSE); //屏蔽错误

/**
 * p输出调试
 * @param $var
 * @return array
 */
function p($var) {
    foreach (func_get_args() as $v) {
        \Symfony\Component\VarDumper\VarDumper::dump($v);
    }
    if (1 < func_num_args()) {
        return func_get_args();
    }
    return $var;
}
/**
 * 快速实例化模型
 * @param $model
 * @return mixed|\think\App
 */
function D($model, $path = '') {
    $model = \think\helper\Str::studly($model); //转换驼峰，首字母大写
    if (!empty($path)) {
        $modelName = "app\\common\\model\\{$path}\\{$model}Model";
    } else {
        $modelName = "app\\common\\model\\{$model}Model";
    }
    return app($modelName);
}

/**
 * 快速实例化CMS模型
 * @param $modelId 取值模型ID或者模型表名
 * @return mixed|\think\App
 */
function cms_model($modelId){
    if(empty($modelId)) return null;
    if(is_numeric($modelId)){
        $tableName = \think\Db::table('cms_moudle')->where('id',$modelId)->value('table_name');
    }else{
        $tableName = $modelId;
    }
    $studlyName = think\helper\Str::studly(str_replace('cms_','',$tableName));
    $modelName = "app\\common\\model\\cms\\{$studlyName}Model";
    return app($modelName);
}

/**
 * 获取模板路径
 * @param $template
 * @param string $depr
 * @param string $viewPath
 * @return string
 */
function T($template,$depr='',$viewPath=''){
    $request = request();
    $config = config('template.');
    $depr = empty($depr) ? $config['view_depr'] : $depr;
    // 获取视图根目录
    if(empty($viewPath)){
        $path = \think\facade\Env::get('APP_PATH') . $request->module() . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR;
    }else{
        $path = \think\facade\Env::get('ROOT_PATH').'public'.DIRECTORY_SEPARATOR.$viewPath;
    }
    // 跨模块调用
    if (strpos($template, '@')) {
        list($module, $template) = explode('@', $template);
        $path = \think\facade\Env::get('APP_PATH') . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR;
    }
    if (0 !== strpos($template, '/')) {
        $template   = str_replace(['/', ':'], $depr, $template);
    }
    if (0 !== strpos($template, '/')) {
        $template   = str_replace(['/', ':'], $depr, $template);
        $controller = \think\Loader::parseName($request->controller());
        if ($controller) {
            if ('' == $template) {
                $rule = [$request->action(true), \think\Loader::parseName($request->action(true)), $request->action()];
                $type = $config['auto_rule'];
                $template = isset($rule[$type]) ? $rule[$type] : $rule[0];
                // 如果模板文件名为空 按照默认规则定位
                $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
            } elseif (false === strpos($template, $depr)) {
                $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
            }
        }
    } else {
        $template = str_replace(['/', ':'], $depr, substr($template, 1));
    }
    return $path . ltrim($template, '/') . '.' . ltrim($config['view_suffix'], '.');
}

/**
 * 重置数组索引，兼容多维数组
 * @param $array
 * @return array
 */
function reformKeys($array) {
    if (!is_array($array)) {
        return $array;
    }
    $keys = implode('', array_keys($array));
    if (is_numeric($keys)) {
        $array = array_values($array);
    }
    $array = array_map('reformKeys', $array);
    return $array;
}

/**
 * 过滤掉emoji表情
 * @param $str
 * @return null|string|string[]
 */
function filterEmoji($str) {
    $str = preg_replace_callback(
        '/./u',
        function (array $match) {
            return strlen($match[0]) >= 4 ? '' : $match[0];
        },
        $str);
    return $str;
}

/**
 *生成18位不重复单据号
 * @return string 生成好的单据号
 */
function rand_number() {
    static $ORDERSN = [];
    $ors = date('ymd') . substr(time(), -5) . substr(microtime(), 2, 5);
    if (isset($ORDERSN[$ors])) {
        $ORDERSN[$ors]++;
    } else {
        $ORDERSN[$ors] = 1;
    }
    return $ors . str_pad($ORDERSN[$ors], 2, '0', STR_PAD_LEFT);
}

/**
 * 字符串截取，支持中文和其他编码
 * @param string $str 源字符串
 * @param int $start 从哪里开始
 * @param int $length 截取的长度
 * @param string $charset 编码，默认utf-8
 * @param bool $suffix 尾部是否添加···,默认添加
 * @return bool|string 截取好的字符串
 */
function msubstr($str, $start = 0, $length, $charset = "utf-8", $suffix = true) {
    if (function_exists("mb_substr"))
        $slice = mb_substr($str, $start, $length, $charset);
    elseif (function_exists('iconv_substr')) {
        $slice = iconv_substr($str, $start, $length, $charset);
        if (false === $slice) {
            $slice = '';
        }
    } else {
        $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
        $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
        $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
        $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
        preg_match_all($re[$charset], $str, $match);
        $slice = join("", array_slice($match[0], $start, $length));
    }
    if ($suffix and mb_strlen($str, 'utf8') > $length) {
        return $slice . '...';
    } else {
        return $slice;
    }
}

/**
 * html代码输出
 * @param string $str 需要操作的字符串
 * @return string 操作后的字符串
 */
function html_out($str) {
    $str = stripslashes(htmlspecialchars_decode($str));
    return $str;
}

/**
 * 内容分页
 * @param string $content 需要拆分的数据
 * @param string $needle 拆分后的数据
 */
function content_page($content, $needle = '_page_break_tag_') {
    //根据分页标识来拆分分页
    $pageContent = explode($needle, html_out($content));
    $page = isset($_GET['cp']) ? intval($_GET['cp']) : 0;
    $contentNowPage = $page > 0 ? $page - 1 : 0;
    echo $pageContent[$contentNowPage]; //输出当前页内容
    $currentUrl = get_current_url();
    $url = strpos($currentUrl, '.html?') !== false ? $currentUrl . '&' : $currentUrl . '?';
    if (($pageCount = count($pageContent)) > 1) {
        $pageHtml = '<div class="clear"></div><div class="page">#content#</div>';
        $pageStr=[];
        for ($i = 1; $i <= $pageCount; $i++) {
            $style = '';
            if ($page == $i) {
                $style = 'class="current"';
            }
            $pageStr[]= '<a href="' . $url . 'cp=' . $i . '" ' . $style . '>' . $i . '</a>';
        }
        $pageHtml = str_replace('#content#',implode(PHP_EOL,$pageStr),$pageHtml);
        echo $pageHtml;
    }
}

/**
 * 提取内容图片地址
 * @param string $content 需要提取的正文
 * @return string 图片地址
 */
function get_img_src($content) {
    $pattern = "/<img(.*?)src=\"(.*?)(?=\")/";
    preg_match_all($pattern, $content, $match);
    if (isset($match[2]) && !empty($match[2])) {
        return $match[2];
    }
    return [];
}

/**
 * 抓取远程图片
 * @param string $url 远程图片地址
 * @param string $filename 保存到本地地址 默认按时间戳微妙数MD5生成
 * @param string $save_dir 保存路径 默认upload/Ymd/ 路径
 * @param int $type 采用何种方式抓取
 * @param array $extend curl获取时有效，额外参数
 * @return array 抓取后的数据数组
 */
function remote_image($url, $filename = '', $save_dir = '', $type = 0, $extend = '') {
    if (trim($url) == '') {
        return ['msg' => '远程地址不能为空！', 'status' => 0];
    }
    if (trim($save_dir) == '') {
        $save_dir = './upload/' . date('Ymd') . '/';
        //创建保存目录
        if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {
            return ['msg' => '文件目录不可写！', 'status' => 0];
        }
        \org\util\FileUtil::createDir($save_dir); //递归创建保存文件夹
    }
    if (trim($filename) == '') {//保存文件名
        //文件头获取文件名
        $mimes = [
            'image/bmp' => '.bmp',
            'image/gif' => '.gif',
            'image/jpeg' => '.jpg',
            'image/png' => '.png',
            'image/x-icon' => '.ico',
        ];
        if (($headers = get_headers($url, 1)) !== false) {
            $type = $headers['Content-Type'];
            $ext = $mimes[$type];
            if ($ext != '.gif' && $ext != '.jpg' && $ext != '.png' && $ext != '.jpeg') {
                return ['msg' => '文件格式不正确！', 'status' => 0];
            }
        } else {
            return ['msg' => '未找到文件头！', 'status' => 0];
        }
        $filename = md5(microtime(true)) . $ext;
    }
    //获取远程文件所采用的方法
    if ($type == 0) {
        $ch = curl_init();
        $timeout = 5;
        if (!empty($extend)) {
            curl_setopt_array($ch, $extend);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        //避免https证书检查
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        $img = curl_exec($ch);
        curl_close($ch);
    } else {
        ob_start();
        readfile($url);
        $img = ob_get_contents();
        ob_end_clean();
    }
    //$size=strlen($img);
    //文件大小
    $fp2 = @fopen($save_dir . $filename, 'a');
    fwrite($fp2, $img);
    fclose($fp2);
    unset($img, $url);
    return ['file_name' => $filename, 'save_path' => $save_dir . $filename, 'status' => 1];
}

/**
 * 把从HTML源码中获取的相对路径转换成绝对路径
 * @param string $url HTML中获取的网址
 * @param string $baseUrl 用来参考判断的原始地址
 * @return 返回修改过的网址，如果网址有误则返回false
 */
function filter_relative_url($url, $baseUrl){
    //STEP1: 先去判断URL中是否包含协议，如果包含说明是绝对地址则可以原样返回
    if(strpos($url, '://') !== FALSE){
        return $url;
    }
    //STEP2: 解析传入的URI
    if(substr($baseUrl,-1)=='/') $baseUrl.='index.html'; //最后一位为/时人为补充一个文件名称
    $urlPart = parse_url($baseUrl);
    if($urlPart == FALSE) return false;
    $rootUrl = $urlPart['scheme'] . '://' . $urlPart['host'] . (isset($urlPart['port']) ? ':' . $urlPart['port'] : '');
    //STEP3: 如果URL以左斜线开头，表示位于根目录
    if(strpos($url, '/') === 0){
        return $rootUrl . $url;
    }
    //STEP4: 不位于根目录，也不是绝对路径，考虑如果不包含'./'的话，需要把相对地址接在原URL的目录名上
    $urlDir = (isset($urlPart['path']) && $urlPart['path']) ? '/' . ltrim(dirname($urlPart['path']), '/')  : '';
    if(strpos($url, './') === FALSE){
        if($urlDir != ''){
            return $rootUrl . $urlDir . '/' . $url;
        } else {
            return $rootUrl . '/' . $url;
        }
    }
    //STEP5: 如果相对路径中包含'../'或'./'表示的目录，需要对路径进行解析并递归
    //STEP5.1: 把路径中所有的'./'改为'/'，'//'改为'/'
    $url = preg_replace('/[^\.]\.\/|\/\//', '/', $url);
    if(strpos($url, './') === 0)
        $url = substr($url, 2);
    //STEP5.2: 使用'/'分割URL字符串以获取目录的每一部分进行判断
    $urlFullDir = ltrim($urlDir . '/' . $url, '/');
    $urlArr = explode('/', $urlFullDir);
    if($urlArr[0] == '..') return false;
    //因为数组的第一个元素不可能为'..'，所以这里从第二个元素开始循环
    $dstArr = $urlArr;  //拷贝一个副本，用于最后组合URL
    for($i = 1; $i < count($urlArr); $i ++){
        if($urlArr[$i] == '..'){
            $j = 1;
            while(TRUE){
                if(isset($dstArr[$i - $j]) && $dstArr[$i - $j] != false){
                    $dstArr[$i - $j] = false;
                    $dstArr[$i] = false;
                    break;
                } else {
                    $j ++;
                }
            }
        }
    }
    // 组合最后的URL并返回
    $dstStr = $rootUrl;
    foreach($dstArr as $val){
        if($val != FALSE) $dstStr .= '/' . $val;
    }
    return $dstStr;
}

/**
 * 精确时间间隔函数
 * @param string $str 输出格式 如 Y-m-d H:i:s
 * @param int $time 发布时间 如1356973323
 * @return false|string 时间字符串
 */
function format_time($str = 'm-d', $time) {
    $way = time() - $time;
    if ($way < 60) {
        $r = '刚刚';
    } elseif ($way >= 60 && $way < 3600) {
        $r = floor($way / 60) . '分钟前';
    } elseif ($way >= 3600 && $way < 86400) {
        $r = floor($way / 3600) . '小时前';
    } elseif ($way >= 86400 && $way < 2592000) {
        $r = floor($way / 86400) . '天前';
    } elseif ($way >= 2592000 && $way < 15552000) {
        $r = floor($way / 2592000) . '个月前';
    } else {
        $r = date("$str", $time);
    }
    return $r;
}

/**
 * 格式化文件大小
 * @param string $size 文件大小
 * @return string 格式化后的文件大小
 */
function get_file_size($size) {
    // 单位自动转换函数
    $kb = 1024;         // Kilobyte
    $mb = 1024 * $kb;   // Megabyte
    $gb = 1024 * $mb;   // Gigabyte
    $tb = 1024 * $gb;   // Terabyte

    if ($size < $kb) {
        return $size . " B";
    } else if ($size < $mb) {
        return round($size / $kb, 2) . " KB";
    } else if ($size < $gb) {
        return round($size / $mb, 2) . " MB";
    } else if ($size < $tb) {
        return round($size / $gb, 2) . " GB";
    } else {
        return round($size / $tb, 2) . " TB";
    }
}


/**
 * 以get模拟网络请求
 * @param string $url HTTP请求URL地址
 * @param array $query GET请求参数
 * @param array $options CURL参数
 * @return boolean|string
 */
function http_get($url, $query = [], $options = [])
{
    return \org\util\Http::get($url, $query, $options);
}


/**
 * 以get模拟网络请求
 * @param string $url HTTP请求URL地址
 * @param array $data POST请求数据
 * @param array $options CURL参数
 * @return boolean|string
 */
function http_post($url, $data, $options = [])
{
    return \org\util\Http::post($url, $data, $options);
}

/**
 * 是否手机端
 * @return int 1、手机端 2电脑端 3微信端
 */
function is_mobile() {
    if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false) {
        return 3;
    }
    // 如果有HTTP_X_WAP_PROFILE则一定是移动设备
    if (isset($_SERVER['HTTP_X_WAP_PROFILE']))
        return 1;
    //此条摘自TPM智能切换模板引擎，适合TPM开发
    if (isset($_SERVER['HTTP_CLIENT']) && 'PhoneClient' == $_SERVER['HTTP_CLIENT'])
        return 1;
    //如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
    if (isset($_SERVER['HTTP_VIA']))
        //找不到为flase,否则为true
        return stristr($_SERVER['HTTP_VIA'], 'wap') ? 1 : 2;
    //判断手机发送的客户端标志,兼容性有待提高
    if (isset($_SERVER['HTTP_USER_AGENT'])) {
        $clientkeywords = [
            'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile',
        ];
        //从HTTP_USER_AGENT中查找手机浏览器的关键字
        if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
            return 1;
        }
    }
    //协议法，因为有可能不准确，放到最后判断
    if (isset($_SERVER['HTTP_ACCEPT'])) {
        // 如果只支持wml并且不支持html那一定是移动设备
        // 如果支持wml和html但是wml在html之前则是移动设备
        if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
            return 1;
        }
    }
    return 2;
}

/**
 * 获取当前url
 * @return string 当前url
 */
function get_current_url() {
    $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
    $url = "{$protocol}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
    return $url;
}

/**
 * 根据两点间的经纬度计算距离
 * @param  float $lat1 纬度值
 * @param  float $lng1 经度值
 * @param  float $lat2 纬度值
 * @param  float $lng2 经度值
 * @return int 距离 单位千米
 */
function get_distance($lat1, $lng1, $lat2, $lng2) {
    $fEARTH_RADIUS = 6378137;
    //角度换算成弧度
    $fRadLon1 = deg2rad($lng1);
    $fRadLon2 = deg2rad($lng2);
    $fRadLat1 = deg2rad($lat1);
    $fRadLat2 = deg2rad($lat2);
    //计算经纬度的差值
    $fD1 = abs($fRadLat1 - $fRadLat2);
    $fD2 = abs($fRadLon1 - $fRadLon2);
    //距离计算
    $fP = pow(sin($fD1 / 2), 2) + cos($fRadLat1) * cos($fRadLat2) * pow(sin($fD2 / 2), 2);
    return intval($fEARTH_RADIUS * 2 * asin(sqrt($fP)) + 0.5);
}

/**
 * 按指定字段分组二维数组
 */
function arr_group($list, $key) {
    $itemlist = [];
    foreach ($list as $v) {
        $itemlist[$v[$key]][] = $v;
    }
    return $itemlist;
}

/**
 * 多维数组排序
 * @param array $arrays 需要操作的数组
 * @param string $sort_key 排序键名
 * @param int $sort_order 排序方式
 * @param int $sort_type 排序方式
 * @return array|bool 排序后的数组
 */
function arr_sort($arrays, $sort_key, $sort_order = SORT_ASC, $sort_type = SORT_NUMERIC) {
    if (is_array($arrays)) {
        foreach ($arrays as $array) {
            if (is_array($array)) {
                $key_arrays[] = $array[$sort_key];
            } else {
                return false;
            }
        }
    } else {
        return false;
    }
    array_multisort($key_arrays, $sort_order, $sort_type, $arrays);
    return $arrays;
}

/**
 * 二维数组最小值最大值
 */
function arr_min_max($arrays, $field) {
    if (!isset($arrays[0])) {
        return ['min' => $arrays[$field], 'max' => $arrays[$field]];
    }
    $min = $arrays[0][$field];
    $max = $arrays[0][$field];
    foreach ($arrays as $key => $val) {
        $max = max($max, $val[$field]);
        $min = min($min, $val[$field]);
    }
    return ['min' => $min, 'max' => $max];
}

/**
 * 人民币小写转大写
 *
 * @param string $number 数值
 * @param string $int_unit 币种单位，默认"元"，有的需求可能为"圆"
 * @param bool $is_round 是否对小数进行四舍五入
 * @param bool $is_extra_zero 是否对整数部分以0结尾，小数存在的数字附加0,比如1960.30，
 *             有的系统要求输出"壹仟玖佰陆拾元零叁角"，实际上"壹仟玖佰陆拾元叁角"也是对的
 * @return string
 */
function num2rmb($number = 0, $int_unit = '元', $is_round = TRUE, $is_extra_zero = FALSE) {
    // 将数字切分成两段
    $parts = explode('.', $number, 2);
    $int = isset($parts[0]) ? strval($parts[0]) : '0';
    $dec = isset($parts[1]) ? strval($parts[1]) : '';

    // 如果小数点后多于2位，不四舍五入就直接截，否则就处理
    $dec_len = strlen($dec);
    if (isset($parts[1]) && $dec_len > 2) {
        $dec = $is_round
            ? substr(strrchr(strval(round(floatval("0." . $dec), 2)), '.'), 1)
            : substr($parts[1], 0, 2);
    }

    // 当number为0.001时，小数点后的金额为0元
    if (empty($int) && empty($dec)) {
        return '零';
    }

    // 定义
    $chs = array('0', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
    $uni = array('', '拾', '佰', '仟');
    $dec_uni = array('角', '分');
    $exp = array('', '万');
    $res = '';

    // 整数部分从右向左找
    for ($i = strlen($int) - 1, $k = 0; $i >= 0; $k++) {
        $str = '';
        // 按照中文读写习惯，每4个字为一段进行转化，i一直在减
        for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
            $u = $int{$i} > 0 ? $uni[$j] : ''; // 非0的数字后面添加单位
            $str = $chs[$int{$i}] . $u . $str;
        }
        //echo $str."|".($k - 2)."<br>";
        $str = rtrim($str, '0');// 去掉末尾的0
        $str = preg_replace("/0+/", "零", $str); // 替换多个连续的0
        if (!isset($exp[$k])) {
            $exp[$k] = $exp[$k - 2] . '亿'; // 构建单位
        }
        $u2 = $str != '' ? $exp[$k] : '';
        $res = $str . $u2 . $res;
    }

    // 如果小数部分处理完之后是00，需要处理下
    $dec = rtrim($dec, '0');

    // 小数部分从左向右找
    if (!empty($dec)) {
        $res .= $int_unit;

        // 是否要在整数部分以0结尾的数字后附加0，有的系统有这要求
        if ($is_extra_zero) {
            if (substr($int, -1) === '0') {
                $res .= '零';
            }
        }

        for ($i = 0, $cnt = strlen($dec); $i < $cnt; $i++) {
            $u = $dec{$i} > 0 ? $dec_uni[$i] : ''; // 非0的数字后面添加单位
            $res .= $chs[$dec{$i}] . $u;
        }
        $res = rtrim($res, '0');// 去掉末尾的0
        $res = preg_replace("/0+/", "零", $res); // 替换多个连续的0
    } else {
        $res .= $int_unit . '整';
    }
    return $res;
}

/**
 * 补全数组
 * @param $arr 要补全数组
 * @param $fieldKey 数组键值
 * @param $fieldValue 数组对应值
 * @param $begin 开始
 * @param $end 结束
 * @return array
 */
function complet_arr($arr, $fieldKey, $fieldValue, $begin, $end) {
    $last = $begin;
    $arr[] = [$fieldKey => $end, $fieldValue => 0]; //添加终点
    foreach ($arr as $v) {
        while ($last < $v[$fieldKey]) {
            $res[] = [$fieldKey => $last, $fieldValue => 0];
            $last += 1;
        }
        $res[] = $v;
        $last += 1;
    }
    return $res;
}

/**
 * 格式化数组
 * @param $var
 * @return false|string
 */
function arr2export($var) {
    $exporter = function ($var, $indent = "") use (&$exporter) {
        switch (gettype($var)) {
            case "string":
                return '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"';
            case "array":
                $self = $exporter;
                $indexed = array_keys($var) === range(0, count($var) - 1);
                $r = [];
                foreach ($var as $key => $value) {
                    $r[] = "$indent    "
                        . ($indexed ? "" : $self($key) . " => ")
                        . $self($value, "$indent    ");
                }
                return "[\n" . implode(",\n", $r) . "\n" . $indent . "]";
            case "boolean":
                return $var ? "TRUE" : "FALSE";
            default:
                return var_export($var, true);
        }
    };
    ob_start();
    array_map(function ($var) use ($exporter) {
        echo $exporter($var);
    }, func_get_args());
    $arr = ob_get_contents();
    ob_end_clean();
    return $arr;
}

/**
 * 发送短信
 * @param $phone
 * @param $data
 * data数组格式
 * [
   content 文字内容，使用在像云片类似的以文字内容发送的平台
   template 模板 ID，使用在以模板ID来发送短信的平台
   data 模板变量，使用在以模板ID来发送短信的平台
 * ]
 * @param $gateways 网关
 * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
 * @throws \Overtrue\EasySms\Exceptions\NoGatewayAvailableException
 */
function send_sms($phone,$data,array $gateways = []){
    try{
        $easySms = new \Overtrue\EasySms\EasySms();
        $easySms->send($phone,$data,$gateways);
    }catch (\Overtrue\EasySms\Exceptions\NoGatewayAvailableException $e){
        \think\facade\Log::write($phone.'发送失败:'.json_encode($e->getExceptions(),JSON_UNESCAPED_UNICODE));
    }
}

/**
 * 发送邮件
 * @param $toAddress
 * @param $subject
 * @param $content
 * @throws \mailer\lib\Exception
 */
function send_email($toAddress,$subject,$content){
    $config = \think\facade\Config::get('email.');
    $mailer = \mailer\tp5\Mailer::instance();
    $mailer->from($config['addr'], $config['name'])
           ->to($toAddress)
           ->subject($subject)
           ->html($content)
           ->send();
}

/**
 * 字符串解密
 * @param $string
 * @param string $key
 * @param int $expiry
 * @return bool|string
 */
function str_decode($string,$key = 'IV0x3J15F3uw', $expiry = 0){
    return str_encode($string,$key,$expiry,'ENCODE');
}
/**
 * authcode加密函数
 * @param $string
 * @param string $operation
 * @param string $key
 * @param int $expiry
 * @return bool|string
 */
function str_encode($string, $key = 'IV0x3J15F3uw', $expiry = 0,$operation = 'DECODE') {
    $m = ['+'=>'75Rym','/'=>'9UqW3'];
    $string = str_replace(array_values($m),array_keys($m),$string);
    // 动态密匙长度，相同的明文会生成不同密文就是依靠动态密匙
    $ckey_length = 4;
    // 密匙
    $key = md5($key);
    // 密匙a会参与加解密
    $keya = md5(substr($key, 0, 16));
    // 密匙b会用来做数据完整性验证
    $keyb = md5(substr($key, 16, 16));
    // 密匙c用于变化生成的密文
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) :
        substr(md5(microtime()), -$ckey_length)) : '';
    // 参与运算的密匙
    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);
    // 明文，前10位用来保存时间戳，解密时验证数据有效性，10到26位用来保存$keyb(密匙b)，
    //解密时会通过这个密匙验证数据完整性
    // 如果是解码的话，会从第$ckey_length位开始，因为密文前$ckey_length位保存 动态密匙，以保证解密正确
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) :
        sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    // 产生密匙簿
    for($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    // 用固定的算法，打乱密匙簿，增加随机性，好像很复杂，实际上对并不会增加密文的强度
    for($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    // 核心加解密部分
    for($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        // 从密匙簿得出密匙进行异或，再转成字符
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if($operation == 'DECODE') { //解密
        // 验证数据有效性，请看未加密明文的格式
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &&
            substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            return substr($result, 26);
        } else {
            return '';
        }
    } else { //加密
        // 把动态密匙保存在密文里，这也是为什么同样的明文，生产不同密文后能解密的原因
        // 因为加密后的密文可能是一些特殊字符，复制过程可能会丢失，所以用base64编码
        $result = str_replace('=', '', base64_encode($result));
        $result = str_replace(array_keys($m), array_values($m), $result); //去除混淆字符
        return $keyc.$result;
    }
}

/**
 * 碎片调用
 * @param $patchSn
 * @return mixed
 */
function get_patch($patchSn){
    $map[]=['patch_sn','=',$patchSn];
    $info = D('Patch')->getInfo($map);
    return $info['patch_content'];
}

/**
 * 获取字典列表
 * @param $classCode
 * @param string $key
 * @param string $value
 * @return array|PDOStatement|string|\think\Collection
 * @throws \think\db\exception\DataNotFoundException
 * @throws \think\db\exception\ModelNotFoundException
 * @throws \think\exception\DbException
 */
function get_dict_list($classCode,$key='id',$value='name'){
    $list = \think\Db::table('dict')
                     ->field("dict_code as {$key},dict_value as {$value},dict_desc")
                     ->where('class_code',$classCode)
                     ->order("show_order asc")
                     ->select();
    $list = $list->isEmpty() ? [] : $list;
    return $list;
}
/**
 * 获取字典键值形式数组
 * @return array
 * @throws \think\db\exception\DataNotFoundException
 * @throws \think\db\exception\ModelNotFoundException
 * @throws \think\exception\DbException
 */
function get_dict_key_value($classCode){
    $list = get_dict_list($classCode);
    if(empty($list)) return [];
    $arr=[];
    foreach($list as $v){
        $arr[$v['id']] = $v['name'];
    }
    return $arr;
}
/**
 * 获取字典值
 * @param $classCode
 * @param $dictCode
 * @return mixed
 */
function get_dict_value($classCode,$dictCode,$key='dict_value'){
    $dict = \think\facade\Cache::get('dict');
    if(isset($dict[$classCode.'-'.$dictCode][$key])){
        return $dict[$classCode.'-'.$dictCode][$key];
    }else{
        return '';
    }
}